//Copyright (c) 2006-2007 Microsoft Corporation.  All rights reserved
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;

using InvocationFramework.Common;
using InvocationFramework.Common.Instrumentation;
using InvocationFramework.Common.Interfaces;
using InvocationFramework.Common.Resources;

namespace InvocationFramework.Core
{
    /// <summary>
    /// Implementation of the Adapter Cache, used by ServiceBridge in order to retain in memory each instance of the adapters.
    /// </summary>
    /// <remarks>
    /// This class is populated during SIF initialization with an instance of each adapter described in SIF client configuration
    /// </remarks>
    /// <author>Rodolfo Finochietti 03/11/2005 10.23</author>
    internal class AdapterCache
    {
        #region Private Vars

        /// <summary>
        /// Flag to indicate if local catalog must be enabled.
        /// </summary>
        private bool _enableLocalCatalog;

        /// <summary>
        /// Dictionary to search adapter instances using the same order as described in configuration.
        /// </summary>
        private SortedDictionary<int, string> _adaptersByOrder = new SortedDictionary<int, string>( );

        /// <summary>
        /// Dictionary to search adapter instances. Used to load the most requested services. 
        /// </summary>
        private SortedDictionary<int, string> _adaptersByOrderDesc = new SortedDictionary<int, string>( );

        /// <summary>
        /// Collection to retain in memory adapter instances.
        /// </summary>
        private Dictionary<string, IServiceAdapter> _adapters = new Dictionary<string, IServiceAdapter>( );

        /// <summary>
        /// Service catalog to store the name of the services provided by each adapter.
        /// </summary>
        private Dictionary<string, string> _servicesCatalog = new Dictionary<string, string>( );

        /// <summary>
        /// Operation catalog to store the operations provided by each service.
        /// </summary>
        private Dictionary<string, string> _operationsCatalog = new Dictionary<string, string>( );

        /// <summary>
        /// Timer to manage catalog invalidation
        /// </summary>
        private System.Timers.Timer _invalidationCatalogTimer;

        /// <summary>
        /// Lock object to synchronize operations over the adapter collection
        /// </summary>
        private ReaderWriterLock syncAdapters = new ReaderWriterLock( );

        /// <summary>
        /// Lock object to synchronize operations over the service catalog collection.
        /// </summary>
        private ReaderWriterLock syncServicesCatalog = new ReaderWriterLock( );

        /// <summary>
        /// Lock object to synchronize operations over the service operation catalog collection.
        /// </summary>
        private ReaderWriterLock syncOperationsCatalog = new ReaderWriterLock( );

        #endregion

        #region Construtors

        /// <summary>
        /// Default constructor
        /// </summary>
        /// <param name="enableLocalCatalog">Indicates if local catalog should be enabled</param>
        /// <param name="localCatalogInvalidationInterval">Time interval in which catalog invalidation occurs</param>
        public AdapterCache( bool enableLocalCatalog, double localCatalogInvalidationInterval )
        {
            _enableLocalCatalog = enableLocalCatalog;

            //If catalog is not enabled, there is no need to activate the timer
            if ( enableLocalCatalog && localCatalogInvalidationInterval > 0 )
            {
                _invalidationCatalogTimer = new System.Timers.Timer( localCatalogInvalidationInterval * 60000 );
                _invalidationCatalogTimer.Elapsed += new System.Timers.ElapsedEventHandler( _invalidationCatalogTimer_Elapsed );
                _invalidationCatalogTimer.Start( );
            }
        }

        #endregion

        #region Eventos

        /// <summary>
        /// Event fired to invalidate the catalog 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _invalidationCatalogTimer_Elapsed( object sender, System.Timers.ElapsedEventArgs e )
        {
            //Invalidate the catalog each time is updated
            syncServicesCatalog.AcquireWriterLock( Constants.LockTimeout );
            try
            {
                //Service catalog invalidation
                _servicesCatalog = null;
                _servicesCatalog = new Dictionary<string, string>( );
            }
            finally
            {
                syncServicesCatalog.ReleaseWriterLock( );
            }

            UpdateCatalogWithMostRequestedServices( );

            syncOperationsCatalog.AcquireWriterLock( Constants.LockTimeout );
            try
            {
                //Operation catalog invalidation
                _operationsCatalog = null;
                _operationsCatalog = new Dictionary<string, string>( );
            }
            finally
            {
                syncOperationsCatalog.ReleaseWriterLock( );
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Updates service catalog with most requested services
        /// </summary>
        public void UpdateCatalogWithMostRequestedServices( )
        {
            syncAdapters.AcquireReaderLock( Constants.LockTimeout );
            try
            {
                //Update only if catalog is enabled
                if ( _enableLocalCatalog )
                {
                    //Loop adapters in descending orders 
                    foreach ( KeyValuePair<int, string> entry in _adaptersByOrderDesc )
                    {
                        //Gets the adapter
                        IServiceAdapter adapter = _adapters[ entry.Value ];

                        UpdateServicesCatalog( adapter.Id, adapter.GetMostRequestedServices( ) );
                    }
                }
            }
            finally
            {
                syncAdapters.ReleaseReaderLock( );
            }
        }

        /// <summary>
        /// Adds an adapter to cache
        /// </summary>
        /// <param name="adapter">Adapter to add</param>
        /// <param name="order">Order of priority</param>
        public void AddAdapter( IServiceAdapter adapter, int order )
        {
            string adapterId = adapter.Id;

            syncAdapters.AcquireWriterLock( Constants.LockTimeout );
            try
            {
                //Agrego el adaptador a una coleccion utilizando el orden como key para despues poder buscarlos por orden
                _adaptersByOrder.Add( order, adapterId );
                _adaptersByOrderDesc.Add( order * -1, adapterId ); //Mantengo tambien un orden inverso para poder recorrer los adaptores al revez

                //Agrego el adaptador al cache de adaptadores
                _adapters.Add( adapterId, adapter );
            }
            finally
            {
                syncAdapters.ReleaseWriterLock( );
            }

            TraceHelper.Verbose( String.Format( CultureInfo.InvariantCulture, Texts.ServiceBridgeAdapterCacheAddAdapterTrace, adapterId ) );
        }

        /// <summary>
        /// Obtains an adapter from cache
        /// </summary>
        /// <param name="adapterId">ID of the adapter</param>
        /// <returns>Returns the requested adapter of null, if no adapter was found in cache</returns>
        public IServiceAdapter GetAdapter( string adapterId )
        {
            IServiceAdapter adapter = null;

            syncAdapters.AcquireReaderLock( Constants.LockTimeout );
            try
            {
                if ( _adapters.ContainsKey( adapterId ) )
                    adapter = _adapters[ adapterId ];
            }
            finally
            {
                syncAdapters.ReleaseReaderLock( );
            }

            return adapter;
        }

        /// <summary>
        /// Finds the name of the adapter that can invoke the service passed by parameter
        /// </summary>
        /// <param name="serviceName">Name of the service</param>
        /// <returns>ID of the adapter or null</returns>
        public string GetAdapterByService( string serviceName )
        {
            string adapterId = null;

            syncServicesCatalog.AcquireReaderLock( Constants.LockTimeout );
            try
            {
                //Busco si tengo registrado el servicio pedido en el catalogo local
                if ( _servicesCatalog.ContainsKey( serviceName ) )
                    adapterId = _servicesCatalog[ serviceName ];
            }
            finally
            {
                syncServicesCatalog.ReleaseReaderLock( );
            }

            //Si no lo encuentro en el catalogo local lo busco utilizando los adapadores
            if ( adapterId == null )
                adapterId = SearchServiceByAdapters( serviceName );

            return adapterId;
        }

        /// <summary>
        /// Finds the name of the adapter that can invoke the service-operation passed by parameter
        /// </summary>
        /// <param name="serviceName">Name of the service</param>
        /// <param name="operationService">name of the operation</param>
        /// <returns>The id of the adapter or null</returns>
        public string GetAdapterByServiceOperation( string serviceName, string operationService )
        {
            string adapterId = null;
            bool contains;

            syncOperationsCatalog.AcquireReaderLock( Constants.LockTimeout );
            try
            {
                //Busco si tengo registrado el servicio pedido en el catalogo local
                contains = _operationsCatalog.ContainsKey( GetOperationCatalogKey( serviceName, operationService ) );
            }
            finally
            {
                syncOperationsCatalog.ReleaseReaderLock( );
            }

            syncServicesCatalog.AcquireReaderLock( Constants.LockTimeout );
            try
            {
                if ( contains )
                    adapterId = _servicesCatalog[ serviceName ];
            }
            finally
            {
                syncServicesCatalog.ReleaseReaderLock( );
            }

            //Si no lo encuentro en el catalogo local lo busco utilizando los adapadores
            if ( adapterId == null )
                adapterId = SearchOperationServiceByAdapters( serviceName, operationService );

            return adapterId;
        }

        /// <summary>
        /// Returns the local catalog list
        /// </summary>
        /// <returns>String array containing the name of services</returns>
        public string[] GetCatalog( )
        {
            List<string> result = new List<string>( );

            syncServicesCatalog.AcquireReaderLock( Constants.LockTimeout );
            try
            {
                foreach ( KeyValuePair<string, string> entry in _servicesCatalog )
                    result.Add( entry.Key );
            }
            finally
            {
                syncServicesCatalog.ReleaseReaderLock( );
            }

            return result.ToArray( );
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Asks each adapter loaded on cache about if a certain service is provided
        /// </summary>
        /// <param name="serviceName">Name of the service</param>
        /// <returns>ID of the adapter that provides the requested service</returns>
        private string SearchServiceByAdapters( string serviceName )
        {
            string adapterId = null;

            syncAdapters.AcquireReaderLock( Constants.LockTimeout );
            try
            {
                //Recorro la coleccion ordenada de adapters por que se debe buscar el servicio de acuerdo al orden de los adapters en la configuracion
                foreach ( KeyValuePair<int, string> entry in _adaptersByOrder )
                {
                    //Busco el adapter
                    IServiceAdapter adapter = _adapters[ entry.Value ];

                    //Pregunto al adaptador si tiene el servicio declarado 
                    if ( adapter.IsServiceAvailable( serviceName ) )
                    {
                        //Si el adaptador tiene registrado al servicio que estoy buscando devuelvo ese id de adaptador
                        adapterId = adapter.Id;

                        //Agrego al catalogo local el servicio para no tener que buscarlo al proxima vez
                        UpdateServicesCatalog( adapterId, serviceName );

                        //Salgo por que se asume que el primer adaptador que tenga ese nombre de servicio declarado es el que se devuelve
                        break;
                    }
                }
            }
            finally
            {
                syncAdapters.ReleaseReaderLock( );
            }

            return adapterId;
        }

        /// <summary>
        /// Asks each adapter loaded on cache about if a certain service-operation is provided
        /// </summary>
        /// <param name="serviceName">Name of the service</param>
        /// <param name="operationName">Name of the operation</param>
        /// <returns>ID of the adapter that provides the requested service operation</returns>
        private string SearchOperationServiceByAdapters( string serviceName, string operationName )
        {
            string adapterId = null;

            syncAdapters.AcquireReaderLock( Constants.LockTimeout );
            try
            {
                //Recorro la coleccion ordenada de adapters por que se debe buscar el servicio de acuerdo al orden de los adapters en la configuracion
                foreach ( KeyValuePair<int, string> entry in _adaptersByOrder )
                {
                    //Busco el adapter
                    IServiceAdapter adapter = _adapters[ entry.Value ];

                    //Pregunto al adaptador si tiene la operacion 
                    if ( adapter.IsServiceOperationAvailable( serviceName, operationName ) )
                    {
                        //Si el adaptador tiene registrado la operacion que estoy buscando devuelvo ese id de adaptador
                        adapterId = adapter.Id;

                        //Agrego al catalogo local la operacion para no tener que buscarlo al proxima vez
                        UpdateServicesOperationCatalog( adapterId, serviceName, operationName );

                        //Salgo por que se asume que el primer adaptador que tenga ese nombre de servicio declarado es el que se devuelve
                        break;
                    }
                }
            }
            finally
            {
                syncAdapters.ReleaseReaderLock( );
            }

            return adapterId;
        }

        /// <summary>
        /// Updates the service catalog 
        /// </summary>
        /// <param name="adapterId">ID of the adapter to update in catalog</param>
        /// <param name="services">List of service names that can be invoked with the adapter</param>
        private void UpdateServicesCatalog( string adapterId, params string[] services )
        {
            //Actualizo el catalogo solo si su utilizacion esta habilitada
            if ( _enableLocalCatalog && services != null )
            {
                syncServicesCatalog.AcquireWriterLock( Constants.LockTimeout );
                try
                {
                    foreach ( string service in services )
                        _servicesCatalog[ service ] = adapterId;
                }
                finally
                {
                    syncServicesCatalog.ReleaseWriterLock( );
                }
            }
        }

        /// <summary>
        /// Update the service operation catalog
        /// </summary>
        /// <param name="adapterId">ID of the adapter to update in catalog</param>
        /// <param name="serviceName">Name of the service</param>
        /// <param name="operationName">Name of the operation</param>
        private void UpdateServicesOperationCatalog( string adapterId, string serviceName, string operationName )
        {
            syncOperationsCatalog.AcquireWriterLock( Constants.LockTimeout );
            try
            {
                //Actualizo el catalogo solo si su utilizacion esta habilitada
                if ( _enableLocalCatalog )
                    _operationsCatalog[ GetOperationCatalogKey( serviceName, operationName ) ] = adapterId;
            }
            finally
            {
                syncOperationsCatalog.ReleaseWriterLock( );
            }
        }

        /// <summary>
        /// Returns the catalog key to seach in the operation catalog.
        /// </summary>
        /// <param name="serviceName">Nombre del servicio</param>
        /// <param name="operationName">Nombre de la operacion</param>
        /// <returns>La clave formateada</returns>
        private static string GetOperationCatalogKey( string serviceName, string operationName )
        {
            return String.Format( CultureInfo.InvariantCulture, "{0}{1}{2}", serviceName, Constants.SeparatorKey, operationName );
        }

        #endregion
    }
}
